// https://man7.org/linux/man-pages/man2/pipe.2.html
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include "prompt.h"

#define MAX_INPUT_SIZE 1024

// global job list
Job_t *job_list = NULL;

void print_prompt(void)
{
    char cwd[1024]; // Buffer to store the current working directory
    // Get the current working directory
    if (getcwd(cwd, sizeof(cwd)) != NULL)
    {
        // Extract the basename of the current directory
        if (strcmp(cwd, "/") == 0)
        {
            printf("[nyush /]$ ");
            fflush(stdout);
            return;
        }

        char *basename = strrchr(cwd, '/');
        if (basename != NULL)
        {
            basename++; // Move past the last '/'
        }
        else
        {
            basename = cwd; // If there are no '/', use the whole path
        }
        printf("[nyush %s]$ ", basename);
        fflush(stdout);
    }
    else
    {
        perror("getcwd");
    }
}

void list_jobs(void)
{
    Job_t *job = job_list;
    int id = 1;
    while (job != NULL)
    {
        printf("[%d] %s\n", id, job->cmd);
        job = job->next;
        id++;
    }
}

void add_job(pid_t pid, char *cmd)
{
    Job_t *job = (Job_t *)malloc(sizeof(Job_t));
    job->pid = pid;
    strcpy(job->cmd, cmd);
    job->next = NULL;

    if (job_list == NULL)
    {
        job_list = job;
    }
    else
    {
        Job_t *curr = job_list;
        while (curr->next != NULL)
        {
            curr = curr->next;
        }
        curr->next = job;
    }
}

Job_t *pop_job(int id)
{
    Job_t *job = job_list;
    Job_t *prev = NULL;
    int jid = 1;

    while (job != NULL)
    {
        if (jid == id)
        {
            if (prev == NULL)
            {
                job_list = job->next;
            }
            else
            {
                prev->next = job->next;
            }
            return job;
        }
        prev = job;
        job = job->next;
        jid++;
    }

    return NULL;
}

bool jobs_is_empty(void)
{
    return job_list == NULL;
}

int cd_command(int argc, char **args)
{
    if (args[1] == NULL || argc > 2)
    {
        fprintf(stderr, "Error: invalid command\n");
    }
    else
    {
        if (chdir(args[1]) < 0)
        {
            fprintf(stderr, "Error: invalid directory\n");
        }
    }

    return 1; // Continue the shell loop
}

// Function to handle the exit command
int exit_command(int argc, char **args)
{
    if (argc >= 2 || args[1] != NULL)
    {
        fprintf(stderr, "Error: invalid command\n");
    }
    else
    {
        if (!jobs_is_empty()) // Check if there are suspended jobs
        {
            fprintf(stderr, "Error: there are suspended jobs\n");
        }
        else
        {
            return -1; // Exit the shell loop
        }
    }

    return 1; // Continue the shell loop
}

// Function to handle the jobs command
int jobs_command(int argc, char **args)
{
    if (argc >= 2 || args[1] != NULL)
    {
        fprintf(stderr, "Error: invalid command\n");
    }
    else
    {
        list_jobs();
    }

    return 1; // Continue the shell loop
}

// Function to handle the fg command
int fg_command(int argc, char **args)
{
    if (args[1] == NULL || argc > 2)
    {
        fprintf(stderr, "Error: invalid command\n");
    }
    else
    {
        int id = atoi(args[1]);   // Get the job id
        Job_t *job = pop_job(id); // Remove the job from the job list

        if (job == NULL)
        {
            fprintf(stderr, "Error: invalid job\n");
        }
        else
        {
            pid_t pid = job->pid;
            char cmd[MAX_COMMAND_LENGTH] = {0};
            strcpy(cmd, job->cmd);
            free(job); // Free the job struct

            int status;
            kill(pid, SIGCONT);               // Send SIGCONT signal to the process
            waitpid(pid, &status, WUNTRACED); // Wait for the process to terminate or stop
            if (WIFSTOPPED(status))
            {
                // If the process is stopped, add it to the job list
                add_job(pid, cmd);
            }
        }
    }

    return 1; // Continue the shell loop
}

void package_cmd(int argc, char **args, char *cmd)
{
    for (int i = 0; i < argc; i++)
    {
        strcat(cmd, args[i]);
        if (i != argc - 1)
        {
            strcat(cmd, " ");
        }
    }
}

int other_command(int argc, char **args)
{
    int status = 0;
    int redirect_input = -1;    // index of "<" in args
    int redirect_output = -1;   // index of ">" in args
    bool is_append = false;     // check if ">>" is in args
    bool more_redirect = false; // check if there are more than one redirection
    int next_argc = 0;          // store the number of args after "|"
    char **next_args = NULL;    // store the next command after "|" in args

    char usr_bin_path[1024] = {0};

    // get the path of usr/bin
    if (strchr(args[0], '/') == NULL && access(args[0], F_OK) == -1)
    {
        strcpy(usr_bin_path, "/usr/bin/");
        strcat(usr_bin_path, args[0]);
    }
    else
    {
        strcpy(usr_bin_path, args[0]);
    }

    if (access(usr_bin_path, F_OK) == -1)
    {
        fprintf(stderr, "Error: invalid program\n");
        return 1;
    }

    for (int i = 0; i < argc; i++)
    {
        if (strcmp(args[i], "<") == 0)
        {
            redirect_input = i + 1;
            args[i] = NULL;
        }
        else if (strcmp(args[i], ">") == 0)
        {
            redirect_output = i + 1;
            more_redirect = true;
            args[i] = NULL;
        }
        else if (strcmp(args[i], ">>") == 0)
        {
            redirect_output = i + 1;
            more_redirect = true;
            is_append = true;
            args[i] = NULL;
        }
        else if (strcmp(args[i], "|") == 0)
        {
            args[i] = NULL;
            next_args = args + i + 1;
            next_argc = argc - i - 1;
            more_redirect = true;
            break;
        }
    }

    if (!more_redirect)
    {
        pid_t pid = fork();
        if (pid == 0)
        {
            signal(SIGINT, SIG_DFL);
            signal(SIGTSTP, SIG_DFL);
            signal(SIGSTOP, SIG_DFL);
            if (redirect_input >= 0)
            {
                int fd = open(args[redirect_input], O_RDONLY, 0777);
                if (fd < 0)
                {
                    fprintf(stderr, "Error: invalid file\n");
                    exit(-1);
                }

                if (dup2(fd, STDIN_FILENO) == -1)
                {
                    fprintf(stderr, "Error: dup2 failed\n");
                }
                redirect_input = -1;
                close(fd);
            }

            if (execvp(args[0], args) < 0)
            {
                fprintf(stderr, "Error: invalid program\n");
            }

            exit(-1);
        }

        // parent process
        waitpid(pid, &status, WUNTRACED);
        if (WIFSTOPPED(status))
        {
            char cmd[MAX_COMMAND_LENGTH] = {0};
            package_cmd(argc, args, cmd);
            add_job(pid, cmd);
        }

        return 0;
    }

    // more than one redirection
    int pipefd[2];
    if (pipe(pipefd) == -1)
    {
        fprintf(stderr, "Error: pipe failed\n");
    }

    pid_t pid_1 = fork();
    if (pid_1 == 0)
    {
        signal(SIGINT, SIG_DFL);
        signal(SIGTSTP, SIG_DFL);
        signal(SIGSTOP, SIG_DFL);
        close(pipefd[0]); // close read end

        if (redirect_input >= 0)
        {
            int fd = open(args[redirect_input], O_RDONLY, 0777);
            if (fd < 0)
            {
                fprintf(stderr, "Error: invalid file\n");
                exit(-1);
            }

            if (dup2(fd, STDIN_FILENO) == -1)
            {
                fprintf(stderr, "Error: dup2 failed\n");
            }
            redirect_input = -1;
            close(fd);
        }

        // redirect stdout to pipe
        if (dup2(pipefd[1], STDOUT_FILENO) == -1)
        {
            fprintf(stderr, "Error: dup2 failed\n");
        }
        close(pipefd[1]);

        if (execvp(args[0], args) < 0)
        {
            fprintf(stderr, "Error: invalid program\n");
        }

        exit(-1);
    }

    pid_t pid_2 = fork();
    if (pid_2 == 0)
    {
        signal(SIGINT, SIG_DFL);
        signal(SIGTSTP, SIG_DFL);
        signal(SIGSTOP, SIG_DFL);
        close(pipefd[1]); // close write end

        if (redirect_output >= 0)
        {
            int fd;
            if (is_append)
            {
                fd = open(args[redirect_output], O_WRONLY | O_CREAT | O_APPEND, 0777);
            }
            else
            {
                fd = open(args[redirect_output], O_WRONLY | O_CREAT | O_TRUNC, 0777);
            }

            if (fd < 0)
            {
                fprintf(stderr, "Error: invalid file\n");
            }

            char ch;
            while (read(pipefd[0], &ch, 1) > 0)
            {
                write(fd, &ch, 1);
            }

            redirect_output = -1;
            close(fd);
        }
        else
        {
            // redirect stdin to pipe
            if (dup2(pipefd[0], STDIN_FILENO) == -1)
            {
                fprintf(stderr, "Error: dup2 failed\n");
            }
            close(pipefd[0]);

            other_command(next_argc, next_args);
        }

        exit(0);
    }
    else
    {
        close(pipefd[0]);
        close(pipefd[1]);

        waitpid(pid_1, &status, WUNTRACED);
        if (WIFSTOPPED(status))
        {
            char cmd[MAX_COMMAND_LENGTH] = {0};
            package_cmd(argc, args, cmd);
            add_job(pid_1, cmd);
        }

        waitpid(pid_2, &status, WUNTRACED);
        if (WIFSTOPPED(status))
        {
            char cmd[MAX_COMMAND_LENGTH] = {0};
            package_cmd(argc, args, cmd);
            add_job(pid_2, cmd);
        }
    }

    return 1; // Continue the shell loop
}

int execute_command(int argc, char **args)
{
    if (args[0] == NULL)
    {
        // Empty command, continue the loop
        return 1;
    }

    if (strcmp(args[0], ">") == 0 || strcmp(args[0], "<") == 0 ||
        strcmp(args[0], "|") == 0 || strcmp(args[0], ">>") == 0)
    {
        fprintf(stderr, "Error: invalid command\n");
        return 1;
    }

    if (argc > 1)
    {
        if (strcmp(args[argc - 1], ">") == 0 || strcmp(args[argc - 1], "<") == 0 ||
            strcmp(args[argc - 1], "|") == 0 || strcmp(args[argc - 1], ">>") == 0)
        {
            fprintf(stderr, "Error: invalid command\n");
            return 1;
        }
    }

    // builtin commands
    if (strcmp(args[0], "cd") == 0)
    {
        // Handle the cd command
        return cd_command(argc, args);
    }
    else if (strcmp(args[0], "exit") == 0)
    {
        // Handle the exit command
        return exit_command(argc, args);
    }
    else if (strcmp(args[0], "jobs") == 0)
    {
        // Call the "jobs" function to list suspended jobs
        return jobs_command(argc, args);
    }
    else if (strcmp(args[0], "fg") == 0)
    {
        return fg_command(argc, args);
    }
    else
    {
        // Handle other commands
        return other_command(argc, args);
    }

    return 0;
}
